package org.hoefling.bean.auction;

import de.dailab.jiactng.agentcore.comm.ICommunicationAddress;
import de.dailab.jiactng.agentcore.comm.message.JiacMessage;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.hoefling.ontology.AuctionProtocol;
import org.hoefling.ontology.Message;
import org.hoefling.ontology.MoneyInfo;

/**
 *
 * @author Kseniya
 */
public class Initiator extends AuctionMember {

    private AuctionStatus currAuction;
    private static final int MAX_WAITING_TIME = 2000;
    private static final int MAX_REG_CALLS = 3;
    private static final int INITIAL_PRICE = 0;
    private static final Object MUTEX = new Object();
    private int registrationCalls;
    private AtomicLong clocks = new AtomicLong();
    private int itemAmount = 8;
    private AtomicInteger currPrice = new AtomicInteger(0);
    private ICommunicationAddress maxBidder;
    private boolean opened = false;
    private final Set<ICommunicationAddress> registeredBidders = new HashSet<>();

    @Override
    public void execute() {
        // register participants
        if (!opened) {
            log.error("Opening auction...");
            sendMsgGroup(new Message(AuctionProtocol.OPENING));
            registrationCalls++;
            if (registrationCalls > MAX_REG_CALLS) {
                opened = true;
                currAuction = AuctionStatus.IDLE;
            }
            return;
        }
        if (AuctionStatus.IDLE == currAuction) {
            if (itemAmount <= 0) {
                log.error("All items sold, auction closed!");
                sendMsgGroup(new Message(AuctionProtocol.CLOSED));
                return;
            }
            log.error("Starting new auction...");
            // initiate registration procedure if necessary
            sendMsgGroup(new MoneyInfo(AuctionProtocol.STARTED, INITIAL_PRICE));
            currAuction = AuctionStatus.READY_TO_START;
            maxBidder = null;
            // start auction in the next execution cycle
            return;
        }
        if (AuctionStatus.READY_TO_START == currAuction) {
            setInitialPrice();
            sendMsgGroup(new MoneyInfo(AuctionProtocol.CALL_FOR_PROPOSALS, currPrice.get()));
            currAuction = AuctionStatus.STARTED;
            clocks.set(System.currentTimeMillis());
            log.error("Auction started! initial price " + currPrice + ", waiting for bids...");
        }
        if (AuctionStatus.STARTED == currAuction) {
//            log.error("milliseconds since last accepted bid: " + (System.currentTimeMillis() - clocks.get()));
            if ((System.currentTimeMillis() - clocks.get()) >= MAX_WAITING_TIME) {
                // auction is over, propose a winner
                String msg;
                if (maxBidder != null) {
                    msg = "item was sold for " + currPrice + " AE.";
                    sendMsgPersonal(new MoneyInfo(AuctionProtocol.REQUEST_WINNER, currPrice.get()), maxBidder);
                } else {
                    msg = "item wasn't sold because no one bid anything";
                }
                sendMsgGroup(new Message(AuctionProtocol.ENDED));
                itemAmount--;
                currAuction = AuctionStatus.IDLE;
                log.error("Auction completed! " + msg);
            }
        }
    }

    @Override
    public void consume(JiacMessage msg) {
        Message payload = (Message) msg.getPayload();
        AuctionProtocol type = (AuctionProtocol) payload.getType();

        switch (type) {
            case REGISTER:
                consumeRegistration(msg.getSender(), ((MoneyInfo) payload).getSum());
                break;
            case PROPOSE:
                consumeProposal(msg.getSender(), ((MoneyInfo) payload).getSum());
                break;
            default:
                break;
        }
    }

    private void consumeRegistration(ICommunicationAddress sender, Integer assets) {
        if (!opened && assets >= Participant.INITIAL_ASSETS) {
            registeredBidders.add(sender);
        }
    }

    private void consumeProposal(ICommunicationAddress sender, Integer price) {
        if (!registeredBidders.contains(sender)) {
            return;
        }
        if (price <= currPrice.get()) {
//            log.error("curr price " + currPrice + ", price " + price + " rejected from " + debugHelp.get(sender));
            sendMsgPersonal(new MoneyInfo(AuctionProtocol.REJECT_PROPOSAL, currPrice.get()), sender);
        } else {
//            log.error("curr price " + currPrice + ", new highest price " + price + " received, resetting clock");
            maxBidder = sender;
            currPrice.set(price);
            clocks.set(System.currentTimeMillis());
            sendMsgPersonal(new MoneyInfo(AuctionProtocol.ACCEPT_PROPOSAL, currPrice.get()), sender);
            sendMsgGroup(new MoneyInfo(AuctionProtocol.CALL_FOR_PROPOSALS, currPrice.get()));
        }
    }

    private void setInitialPrice() {
        currPrice.set(0);
    }

    //<editor-fold defaultstate="collapsed" desc="getter/setter">
    public int getItemAmount() {
        return itemAmount;
    }

    public void setItemAmount(int itemAmount) {
        this.itemAmount = itemAmount;
    }

    //</editor-fold>
    private enum AuctionStatus {

        READY_TO_START,
        STARTED,
        IDLE;
    }
}
